package cn.wapache.time.calendar;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;

import cn.wapache.time.TimeOfDay;

/**
 * jdk的calender的wrapper,builder,helper
 * @author ykuang
 *
 */
public class JdkCalendar {
    
    /** 星期一 ~ 星期日 */
    public static Set<Integer> ALL_DAYS_OF_THE_WEEK;
    
    /** 星期一 ~ 星期五 */
    public static Set<Integer> MONDAY_THROUGH_FRIDAY;
    
    /** 星期六日 */
    public static Set<Integer> SATURDAY_AND_SUNDAY;
    
    public static MonthDayComparator monthDayComparator;
    
    
    static {
    	initialize();
    }
    
    private static void initialize() {
        Set<Integer> t = new HashSet<Integer>(7);
        for(int i=Calendar.SUNDAY; i <= Calendar.SATURDAY; i++){
            t.add(i);
        }
        ALL_DAYS_OF_THE_WEEK = Collections.unmodifiableSet(t);
        
        t = new HashSet<Integer>(5);
        for(int i=Calendar.MONDAY; i <= Calendar.FRIDAY; i++){
            t.add(i);
        }
        MONDAY_THROUGH_FRIDAY = Collections.unmodifiableSet(t);
        
        t = new HashSet<Integer>(2);
        t.add(Calendar.SUNDAY);
        t.add(Calendar.SATURDAY);
        SATURDAY_AND_SUNDAY = Collections.unmodifiableSet(t);
        
        monthDayComparator = new MonthDayComparator();
    }
    
    Calendar innerCalendar;
	
    public static JdkCalendar getInstance(){
		JdkCalendar c = new JdkCalendar();
		c.innerCalendar = Calendar.getInstance();
		return c;
	}
    
    public static JdkCalendar getInstance(Calendar calendar){
		JdkCalendar c = new JdkCalendar();
		c.innerCalendar = calendar;
		return c;
	}
    
    public static JdkCalendar getInstance(long calendar){
		JdkCalendar c = new JdkCalendar();
		c.innerCalendar = Calendar.getInstance();
		c.innerCalendar.setTimeInMillis(calendar);
		return c;
	}
    
    public static JdkCalendar getInstance(TimeZone timeznone){
		JdkCalendar c = new JdkCalendar();
		if(timeznone!=null){
			c.innerCalendar = Calendar.getInstance(timeznone);
		}
		return c;
	}
	
	public static JdkCalendar getInstance(Date date){
		return new JdkCalendar().setTime(date);
	}
	
	public static JdkCalendar getInstance(TimeZone timeznone, Date date){
		return getInstance(timeznone).setTime(date);
	}
	
	public JdkCalendar addSecond(int seconds){
		innerCalendar.add(Calendar.SECOND, seconds);
		return this;
	}
	
	public JdkCalendar addMinute(int minutes){
		innerCalendar.add(Calendar.MINUTE, minutes);
		return this;
	}
	
	public JdkCalendar addHour(int hours){
		innerCalendar.add(Calendar.HOUR_OF_DAY, hours);
		return this;
	}

	public JdkCalendar setTime(Date date){
		innerCalendar.setTime(date);
		return this;
	}
	
	public JdkCalendar setDate(int year,int month,int day){
		innerCalendar.set(Calendar.YEAR, year);
		innerCalendar.set(Calendar.MONTH, month);
		innerCalendar.set(Calendar.DAY_OF_MONTH, day);
		return this;
	}
	
	public JdkCalendar setDateTime(int hourOfDay,int minute,int second,int millisecond){
		innerCalendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
		innerCalendar.set(Calendar.MINUTE, minute);
		innerCalendar.set(Calendar.SECOND, second);
		innerCalendar.set(Calendar.MILLISECOND, millisecond);
		return this;
	}
	
	public JdkCalendar setMillisecond(int milliseconds){
		innerCalendar.set(Calendar.MILLISECOND, milliseconds);
		return this;
	}
	
	public JdkCalendar setSecond(int seconds){
		innerCalendar.set(Calendar.SECOND, seconds);
		return this;
	}
	
	public JdkCalendar setMinute(int minutes){
		innerCalendar.set(Calendar.MINUTE, minutes);
		return this;
	}
	
	public JdkCalendar setHour(int hours){
		innerCalendar.set(Calendar.HOUR_OF_DAY, hours);
		return this;
	}
	
	public Calendar buildCalendar(){
		return innerCalendar;
	}
	
	public Date build(){
		return innerCalendar.getTime();
	}
	
	public long buildInMillis(){
		return innerCalendar.getTimeInMillis();
	}
    
	//----------------------------------------------------------------
    
	public static Calendar getCalendar() {
        return Calendar.getInstance();
    }
	
	public static Calendar getCalendar(Date date){
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		return c;
	}
	
	public static Calendar getCalendar(long timeStamp) {
    	Calendar calendar = getCalendar();
        calendar.setTime(new Date(timeStamp));
        return calendar;
    }
    
	public static Calendar getCalendar(TimeZone timeznone) {
        return (timeznone == null) ? Calendar.getInstance() : Calendar.getInstance(timeznone);
    }
    
	public static Calendar getCalendar(TimeZone timeznone, long timeStamp) {
       Calendar calendar = getCalendar(timeznone);
        calendar.setTime(new Date(timeStamp));
        return calendar;
    }

	public static Calendar getCalendar(Calendar calendar, 
			int hourOfDay,int minute,int second,int millisecond) {
        calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, millisecond);
        return calendar;
    }
	
//	public static Calendar getCalendar(long timeInMillis,
//			int hourOfDay,int minute,int second,int millisecond) {
//		return getCalendar(timeInMillis,null, hourOfDay,minute,second,millisecond);
//	}
	
	public static Calendar getCalendar(TimeZone timeznone, long timeInMillis,
			int hourOfDay,int minute,int second,int millisecond) {
		Calendar calendar = getCalendar(timeznone, timeInMillis);
		calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, millisecond);
        return calendar;
    }
	
	public static Calendar getStartOfDay(long timeInMillis) {
		return setStartOfDay(getCalendar(timeInMillis));
    }

	public static Calendar getStartOfDay(TimeZone timeznone, long timeInMillis) {
        return setStartOfDay(getCalendar(timeznone, timeInMillis));
    }

	public static java.util.Calendar getEndOfDay(long timeInMillis) {
    	return setEndOfDay( getCalendar(timeInMillis));
    }

	public static Calendar getEndOfDay(TimeZone timeznone,long timeInMillis) {
        return setEndOfDay( getCalendar(timeznone, timeInMillis));
    }

	public static Calendar setStartOfDay(Calendar startOfDay) {
        startOfDay.set(Calendar.HOUR_OF_DAY, 0);
        startOfDay.set(Calendar.MINUTE, 0);
        startOfDay.set(Calendar.SECOND, 0);
        startOfDay.set(Calendar.MILLISECOND, 0);
        return startOfDay;
    }
	
	public static Calendar setEndOfDay(Calendar endOfDay) {
        endOfDay.set(java.util.Calendar.HOUR_OF_DAY, 23);
        endOfDay.set(java.util.Calendar.MINUTE, 59);
        endOfDay.set(java.util.Calendar.SECOND, 59);
        endOfDay.set(java.util.Calendar.MILLISECOND, 999);
        return endOfDay;
    }
	
	
	//----------------------------------------------------------------
    
	public static Date now(){
		return new Date();
	}

	public static int getCurrentYear(){
		return Calendar.getInstance().get(java.util.Calendar.YEAR);
	}
	
	/**
	 * 获取年份
	 * @param years 多少年
	 * @return 当前年份 + years
	 */
	public static int getYear(int years){
		return Calendar.getInstance().get(java.util.Calendar.YEAR) + years;
	}

	public static int getYear(Date date){
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		return c.get(java.util.Calendar.YEAR);
	}
	
	public static int getYear(long millis){
		Calendar c = Calendar.getInstance();
		c.setTimeInMillis(millis);
		return c.get(java.util.Calendar.YEAR);
	}

    public static boolean isSameDay(Date d1, Date d2) {
    
      Calendar c1 = getCalendar(d1);
      Calendar c2 = getCalendar(d2);
      
      return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR) && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
    }
    
    public static boolean isSameDay(long d1, long d2) {
        
        Calendar c1 = getCalendar(d1);
        Calendar c2 = getCalendar(d2);
        
        return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR) && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
    }
	
    /**
     * Given fireTime time determine if it is on a valid day of week. If so, simply return it unaltered,
     * if not, advance to the next valid week day, and set the time of day to the start time of day
     * 
     * @param fireTime - given next fireTime.
     * @param forceToAdvanceNextDay - flag to whether to advance day without check existing week day. This scenario
     * can happen when a caller determine fireTime has passed the endTimeOfDay that fireTime should move to next day anyway.
     * @return a next day fireTime.
     */
	
	/**
	 * 取下一个星期几
	 * @param fireTime 初始日期
	 * @param sTimeOfDay 初始时间
	 * @param endTime 截止时间, 返回的时间不能在这个时间之后,超过了这个时间返回null
	 * @param daysOfWeekToFire 星期几 的集合
	 * @param forceToAdvanceNextDay 强制取"下"一天,这一天必须在 "星期几集合" 内
	 *                              当没有符合条件的日期的时候,则返回 "初始日期"
	 * @return 下一个星期几
	 */
    public static Date getNextDayOfWeek(Date fireTime, TimeOfDay startTimeOfDay, Date endTime, 
    		Set<Integer> daysOfWeekToFire, boolean forceToAdvanceNextDay) {
    	
        // a. Advance or adjust to next dayOfWeek if need to first, starting next day with startTimeOfDay.
    	
    	// fireTime的年月日, startTimeOfDay的时分秒
        Date fireTimeStartDate = startTimeOfDay.getTimeOfDayForDate(fireTime);
        
        // 取出星期几
        Calendar fireTimeStartDateCal = getCalendar(fireTimeStartDate);          
        int dayOfWeekOfFireTime = fireTimeStartDateCal.get(Calendar.DAY_OF_WEEK);
        
        // b2. We need to advance to another day if isAfterTimePassEndTimeOfDay is true, or dayOfWeek is not set.

        if (forceToAdvanceNextDay || !daysOfWeekToFire.contains(dayOfWeekOfFireTime)) {
          // Advance one day at a time until next available date.
          for(int i=1; i <= 7; i++) { // 往后挪,直到 星期几集合里有 或者超过7天 为止
            fireTimeStartDateCal.add(Calendar.DATE, 1);
            dayOfWeekOfFireTime = fireTimeStartDateCal.get(Calendar.DAY_OF_WEEK);
            if (daysOfWeekToFire.contains(dayOfWeekOfFireTime)) {
              fireTime = fireTimeStartDateCal.getTime();
              break;
            }
          }
        }
        
         if (endTime != null && fireTime.getTime() > endTime.getTime()) {
             return null;
         }

        return fireTime;
    }

    public static long getNextDayOfWeek(long fireTime, TimeOfDay startTimeOfDay, long endTime, 
    		Set<Integer> daysOfWeekToFire, boolean forceToAdvanceNextDay) {
    	return getNextDayOfWeek(new Date(fireTime), startTimeOfDay, new Date(endTime), daysOfWeekToFire, forceToAdvanceNextDay).getTime();
    }
    
    public static class MonthDayComparator implements Comparator<Calendar>, Serializable {
    	  
        private static final long serialVersionUID = 7346867105876610961L;
        
        public int compare(Calendar c1, Calendar c2) {
            
            int month1 = c1.get(Calendar.MONTH);
            int month2 = c2.get(Calendar.MONTH);
            
            int day1 = c1.get(Calendar.DAY_OF_MONTH);
            int day2 = c2.get(Calendar.DAY_OF_MONTH);
            
            if (month1 < month2) {
                return -1;
            }
            if (month1 > month2) {
                return 1; 
            }
            if (day1 < day2) {
                return -1;
            }
            if (day1 > day2) {
                return 1;
            }
            return 0;
          }
    }
}
